import "./style.css";

import {
  Audio,
  AudioLoader,
  AudioListener,
  AudioAnalyser,
  Scene,
  Clock,
  Color,
  AmbientLight,
  WebGLRenderer,
  DirectionalLight,
  PerspectiveCamera,
} from 'three';

import { MMDLoader } from 'three/examples/jsm/loaders/MMDLoader.js';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';
import { MMDAnimationHelper } from 'three/examples/jsm/animation/MMDAnimationHelper.js';

function onProgress(xhr) {
  console.log(( xhr.loaded / xhr.total * 100 ) + '% loaded');
}
function onError(err) {
  console.log('Error:', err);
}

let mesh;
const fftSize = 128;
let analyser;
let dict;
let defaultDict = {};

const container = document.createElement('div');
document.body.appendChild(container);

function init() {
  const camera = new PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 2000);
  camera.position.set(0, 0, 25);

  const scene = new Scene();
  scene.background = new Color(0xffffff);

  const ambient = new AmbientLight(0x666666);
  scene.add(ambient);

  const directionalLight = new DirectionalLight(0x887766);
  directionalLight.position.set(-1, 1, 1).normalize();
  scene.add(directionalLight);

  let renderer = new WebGLRenderer({ antialias: true });
  // console.log('devicePixelRatio:', window.devicePixelRatio);
  // renderer.setPixelRatio(window.devicePixelRatio);
  renderer.setPixelRatio(1);
  renderer.setSize(window.innerWidth, window.innerHeight);
  container.appendChild(renderer.domElement);

  let cControls = new OrbitControls(camera, renderer.domElement);
  cControls.target.set(0, 7, 0);
  cControls.update();

  const listener = new AudioListener();
  const audio = new Audio(listener);
  const audioFile = './audios/wavefile_short.mp3';

  let ready = false;
  let disabled = false;
  const disableBtn = document.getElementById("disableBtn");
  const pauseBtn = document.getElementById("pauseBtn");

  // if ( /(iPad|iPhone|iPod)/g.test( navigator.userAgent ) ) {
  {
    const loader = new AudioLoader();
    loader.load( audioFile, function ( buffer ) {
        audio.setBuffer( buffer );
        ready = true;
        // audio.play();
    } );
  }

  function startAudio() {
    if (ready) {
      audio.play();
    }
  }

  function toggleDisable() {
    disabled = !disabled;
    if (disabled) {
      disableBtn.innerText = "启用";
    } else {
      disableBtn.innerText = "禁用";
    }
  }

  function togglePause() {
    if (audio.isPlaying) {
      audio.pause();
      pauseBtn.innerText = "继续";
    } else {
      audio.play();
      pauseBtn.innerText = "暂停";
    }
  }

  function onSliderChange(e) {
    
    for (let i = 0; i < keys.length; i++) {
      const key = sliders[i].getAttribute("data-key");
      const value = sliders[i].value;
      mesh.morphTargetInfluences[i] = parseFloat(value);
    }
    render();
  }

  function resetMorph() {
    for (let i = 0; i < keys.length; i++) {
      sliders[i].value = defaultDict[sliders[i].getAttribute("data-key")];
    }
    onSliderChange();
    render();
  }

  document.getElementById("startBtn").addEventListener("click", startAudio);
  disableBtn.addEventListener("click", toggleDisable);
  pauseBtn.addEventListener("click", togglePause);
  document.getElementById("resetBtn").addEventListener("click", resetMorph);

  /* } else {
    const mediaElement = new Audio( audioFile );
    mediaElement.play();

    audio.setMediaElementSource( mediaElement );
  } */

  analyser = new AudioAnalyser( audio, fftSize );

  const modelFile = 'models/fischl.pmx';
  const loader = new MMDLoader();
  const helper = new MMDAnimationHelper();
  loader.load(modelFile, (obj) => {
    mesh = obj;
    mesh.position.y = -10;
    scene.add(mesh);
    dict = mesh.morphTargetDictionary;
    console.log("morph influences:", mesh.morphTargetInfluences);
    initParams();

  }, onProgress, onError);

  function onWindowResize() {
    camera.aspect = window.innerWidth / window.innerHeight;
    camera.updateProjectionMatrix();
    renderer.setSize(window.innerWidth, window.innerHeight);
  }

  window.addEventListener('resize', onWindowResize, false);

  const controls = {};
  const keys = [];
  const sliders = [];

  function initParams() {
    function initControls() {
      for (const key in dict) {
        controls[key] = 0.0;
        keys.push(key);
        defaultDict[key] = 0.0;
      }
      controls.pose = -1;
      console.log("morph dict:", dict);

      const div = document.getElementById("ctls");
      for (const key in dict) {
        const subDiv = document.createElement("div");
        const slider = document.createElement("input");
        slider.setAttribute("type", "range");
        slider.setAttribute("min", 0);
        slider.setAttribute("max", 1);
        slider.setAttribute("step", 0.01);
        slider.setAttribute("data-key", key);
        slider.setAttribute("value", 0.0);
        const span = document.createElement("span");
        span.innerText = key;
        subDiv.appendChild(span);
        subDiv.appendChild(slider);
        sliders.push(slider);
        div.appendChild(subDiv);
      }
      div.addEventListener("input", onSliderChange);

    }
    function initKeys() {
      for (const key in dict) {
        keys.push(key);
      }
    }
    initControls();
  }

  function onChangeMorph(controls) {
    for (let i = 0; i < keys.length; i++) {
      const key = keys[i];
      const value = controls[key];
      sliders[i].value = value;
      mesh.morphTargetInfluences[i] = value;
    }
  }

  function transformValue(v, x=0, y=1) {
    // v: 0-255
    // return: x-y 
    return v / 256 * (y - x) + x;
  }
  const fftMap = {
    0: {
      idx: 0,
      x: 0.35,
      y: 0.4
    },
    5: {
      idx: 54,
      x: 0,
      y: 1
    },
    9: {
      idx: 6,
      x: 0,
      y: 1
    },
    19: {
      idx: 57,
      x: 0,
      y: 1
    },
    20: {
      idx: 48,
      x: 0,
      y: 1
    },
    21: {
      idx: 24,
      x: 0,
      y: 1
    },
    23: {
      idx: 12,
      x: 0,
      y: 1
    },
    27: {
      idx: 36,
      x: 0,
      y: 1,
    },
    30: {
      idx: 30,
      x: 0,
      y: 1
    },
  }
  function fft2Morph(fft) {
    let i = 0, ret = {};
    Object.assign(ret, defaultDict);
    for (const key in fftMap) {
      const val = fftMap[key];
      ret[keys[key]] = transformValue(fft[val.idx], val.x, val.y);
    }
    console.log(ret);
    return ret;
  }

  function animate() {
    requestAnimationFrame(animate);
    // controls.update();
    let fft;
    if (audio.isPlaying) {
      analyser.getFrequencyData();
      fft = analyser.data;
      if (!disabled) {
        const morphs = fft2Morph(fft);
        onChangeMorph(morphs);
      }
    }
    render();
  }
  function render() {
    renderer.render(scene, camera);
  }

  initParams();
  animate();

  cControls.addEventListener('change', render);

}
init();
